package com.sky.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sky.properties.WeChatProperties;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.math.BigDecimal;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;

/**
 * 微信支付工具类
 */
@Component
public class WeChatPayUtil {

    //微信支付下单接口地址
    public static final String JSAPI = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";

    //申请退款接口地址
    public static final String REFUNDS = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";

    @Autowired
    private WeChatProperties weChatProperties;

    /**
     * 获取调用微信接口的客户端工具对象
     *
     * @return CloseableHttpClient 客户端工具对象，用于调用微信接口
     */
    private CloseableHttpClient getClient() {
        // 商户API私钥，用于签名
        PrivateKey merchantPrivateKey = null;
        try {
            // 加载商户API私钥，具体加载方法请参考文档或常见问题
            merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath())));
            // 加载平台证书文件，用于验签
            X509Certificate x509Certificate = PemUtil.loadCertificate(new FileInputStream(new File(weChatProperties.getWeChatPayCertFilePath())));
            // 微信支付平台证书列表，使用定时更新平台证书功能的用户无需关心
            List<X509Certificate> wechatPayCertificates = Arrays.asList(x509Certificate);

            // 创建WechatPayHttpClientBuilder实例并配置商户信息和平台证书
            WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                    .withMerchant(weChatProperties.getMchid(), weChatProperties.getMchSerialNo(), merchantPrivateKey)
                    .withWechatPay(wechatPayCertificates);

            // 通过WechatPayHttpClientBuilder构建的HttpClient，自动处理签名和验签
            CloseableHttpClient httpClient = builder.build();
            return httpClient;
        } catch (FileNotFoundException e) {
            // 文件未找到异常处理，打印异常信息
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 发送POST请求方法
     *
     * 此方法用于向指定URL发送POST请求它设置了一些必要的请求头，
     * 并将请求体作为字符串传递还需要设置微信支付的序列号，
     * 用于微信支付的相关请求
     *
     * @param url 请求的URL地址
     * @param body 请求的主体部分，通常是一个JSON字符串
     * @return 返回请求的响应主体部分
     * @throws Exception 如果在请求过程中发生错误，将抛出异常
     */
    private String post(String url, String body) throws Exception {
        // 获取HttpClient客户端实例
        CloseableHttpClient httpClient = getClient();

        // 创建一个HttpPost对象，设置请求URL
        HttpPost httpPost = new HttpPost(url);

        // 添加请求头，指定接受的数据类型为JSON
        httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
        // 设置请求体的类型为JSON
        httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
        // 设置微信商户的序列号，用于微信支付的请求
        httpPost.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
        // 将请求体作为字符串设置到请求对象中
        httpPost.setEntity(new StringEntity(body, "UTF-8"));

        // 执行HTTP请求，获取响应
        CloseableHttpResponse response = httpClient.execute(httpPost);
        try {
            // 读取响应的主体部分，并将其转换为字符串
            String bodyAsString = EntityUtils.toString(response.getEntity());
            return bodyAsString;
        } finally {
            // 关闭HttpClient客户端实例
            httpClient.close();
            // 关闭响应对象
            response.close();
        }
    }


    /**
     * 发送GET方式的HTTP请求
     *
     * @param url 请求的URL地址，包含完整路径和参数
     * @return 返回请求的响应体内容
     * @throws Exception 如果请求过程中出现错误，抛出异常
     */
    private String get(String url) throws Exception {
        // 创建HttpClient客户端实例，用于发送HTTP请求
        CloseableHttpClient httpClient = getClient();

        // 创建HttpGet实例，指定请求的URL
        HttpGet httpGet = new HttpGet(url);

        // 添加请求头，指定接受的数据类型为JSON
        httpGet.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
        // 设置请求体的类型为JSON
        httpGet.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
        // 添加微信支付序列号到请求头，用于身份验证
        httpGet.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());

        // 执行HTTP GET请求，获取响应
        CloseableHttpResponse response = httpClient.execute(httpGet);
        try {
            // 读取响应实体的内容，并转换为字符串
            String bodyAsString = EntityUtils.toString(response.getEntity());
            return bodyAsString;
        } finally {
            // 关闭HttpClient客户端实例，释放资源
            httpClient.close();
            // 关闭HTTP响应，释放资源
            response.close();
        }
    }


    /**
     * 通过JSAPI（微信支付提供的用于小程序等场景的支付接口）进行下单操作
     *
     * @param orderNum 商户订单号，用于唯一标识每一笔订单
     * @param total    订单总金额，单位为元，需要转换为分进行支付
     * @param description 商品描述，用于支付确认界面的展示
     * @param openid    微信用户的openid，是进行微信支付必须的用户标识
     * @return 返回微信支付所需的预支付交易会话标识
     * @throws Exception 在网络请求或数据处理过程中可能抛出异常
     */
    private String jsapi(String orderNum, BigDecimal total, String description, String openid) throws Exception {
        // 构建微信支付接口所需的请求体
        JSONObject jsonObject = new JSONObject();
        // 应用ID，即商户的appid
        jsonObject.put("appid", weChatProperties.getAppid());
        // 商户号，微信支付为商户提供的唯一标识
        jsonObject.put("mchid", weChatProperties.getMchid());
        // 商品描述，用于支付确认界面的展示
        jsonObject.put("description", description);
        // 商户订单号，用于唯一标识每一笔订单
        jsonObject.put("out_trade_no", orderNum);
        // 商户接收异步通知的回调地址
        jsonObject.put("notify_url", weChatProperties.getNotifyUrl());

        // 构建金额对象，包括总金额和货币类型
        JSONObject amount = new JSONObject();
        // 将金额转换为分，并取整，微信支付的金额单位为分
        amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
        // 货币类型，CNY表示人民币
        amount.put("currency", "CNY");

        // 将金额对象添加到请求体中
        jsonObject.put("amount", amount);

        // 构建支付者对象，包括用户的openid
        JSONObject payer = new JSONObject();
        // 微信用户的openid，是进行微信支付必须的用户标识
        payer.put("openid", openid);

        // 将支付者对象添加到请求体中
        jsonObject.put("payer", payer);

        // 将请求体转换为JSON字符串格式
        String body = jsonObject.toJSONString();
        // 发起网络请求，发送支付请求到微信支付接口
        return post(JSAPI, body);
    }


    /**
     * 小程序支付
     *
     * @param orderNum    商户订单号
     * @param total       金额，单位 元
     * @param description 商品描述
     * @param openid      微信用户的openid
     * @return
     * @throws Exception
     */
    public JSONObject pay(String orderNum, BigDecimal total, String description, String openid) throws Exception {
        // 统一下单，生成预支付交易单
        String bodyAsString = jsapi(orderNum, total, description, openid);
        // 解析返回结果
        JSONObject jsonObject = JSON.parseObject(bodyAsString);
        System.out.println(jsonObject);

        // 获取预支付交易单ID
        String prepayId = jsonObject.getString("prepay_id");
        if (prepayId != null) {
            // 获取当前时间戳
            String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
            // 生成随机字符串
            String nonceStr = RandomStringUtils.randomNumeric(32);
            // 构建签名材料
            ArrayList<Object> list = new ArrayList<>();
            list.add(weChatProperties.getAppid());
            list.add(timeStamp);
            list.add(nonceStr);
            list.add("prepay_id=" + prepayId);
            // 二次签名，调起支付需要重新签名
            StringBuilder stringBuilder = new StringBuilder();
            for (Object o : list) {
                stringBuilder.append(o).append("\n");
            }
            String signMessage = stringBuilder.toString();
            byte[] message = signMessage.getBytes();

            // 使用RSA进行签名
            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initSign(PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath()))));
            signature.update(message);
            String packageSign = Base64.getEncoder().encodeToString(signature.sign());

            // 构造数据给微信小程序，用于调起微信支付
            JSONObject jo = new JSONObject();
            jo.put("timeStamp", timeStamp);
            jo.put("nonceStr", nonceStr);
            jo.put("package", "prepay_id=" + prepayId);
            jo.put("signType", "RSA");
            jo.put("paySign", packageSign);

            return jo;
        }
        return jsonObject;
    }


    /**
     * 申请退款
     *
     * @param outTradeNo    商户订单号
     * @param outRefundNo   商户退款单号
     * @param refund        退款金额
     * @param total         原订单金额
     * @return
     */
    public String refund(String outTradeNo, String outRefundNo, BigDecimal refund, BigDecimal total) throws Exception {
        //构建退款请求的JSON对象
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("out_trade_no", outTradeNo);
        jsonObject.put("out_refund_no", outRefundNo);

        //构建金额对象，包括退款金额、原订单金额和货币类型
        JSONObject amount = new JSONObject();
        amount.put("refund", refund.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
        amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
        amount.put("currency", "CNY");

        //将金额对象添加到请求JSON中
        jsonObject.put("amount", amount);

        //设置退款通知URL
        jsonObject.put("notify_url", weChatProperties.getRefundNotifyUrl());

        //将JSON对象转换为字符串
        String body = jsonObject.toJSONString();

        //调用申请退款接口
        return post(REFUNDS, body);
    }

}
